#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "nweb.h"
#include "worker.h"
#include "mime.h"
#include "nweb_log.h"

void printUsage();
int isValidTopDir(char *webdir);
int startDaemon(int port, char* webdir);

int main(int argc, char **argv) {
	int port;
	char *webdir;

	if (argc < 3 || argc > 3 || !strcmp(argv[1], "-?")) {
		printUsage();
		exit(0);
	}

	port = atoi(argv[1]);
	webdir = argv[2];
	if (!isValidTopDir(webdir)) {
		(void) printf("ERROR: Bad top directory %s, see nweb -?\n", webdir);
		exit(3);
	}
	
	startDaemon(port, webdir);
	return 0;
}

void printUsage() {
	int i;
	(void) printf("hint: nweb Port-Number Top-Directory\n\n"
			"\tnweb is a small and very safe mini web server\n"
			"\tnweb only servers out file/web pages with extensions named below\n"
			"\t and only from the named directory or its sub-directories.\n"
			"\tThere is no fancy features = safe and secure.\n\n"
			"\tExample: nweb 8181 /home/nwebdir &\n\n"
			"\tOnly Supports:");
	for (i = 0; extensions[i].ext != 0; i++)
		(void)printf(" %s", extensions[i].ext);

	(void) printf("\n\tNot Supported: URLs including \"..\", Java, Javascript, CGI\n"
			"\tNot Supported: directories / /etc /bin /lib /tmp /usr /dev /sbin \n"
			"\tNo warranty given or implied\n\tNigel Griffiths nag@uk.ibm.com\n"
			);
}

int isValidTopDir(char *webdir) {
    return strncmp(webdir,"/"   ,2 ) && strncmp(webdir,"/etc", 5 ) &&
	    strncmp(webdir,"/bin",5 ) && strncmp(webdir,"/lib", 5 ) &&
	    strncmp(webdir,"/tmp",5 ) && strncmp(webdir,"/usr", 5 ) &&
	    strncmp(webdir,"/dev",5 ) && strncmp(webdir,"/sbin",6);
}

int startDaemon(int port, char* webdir) {
	int i, pid, listenfd, socketfd, hit;
	char port_str[16];
	size_t length;
	static struct sockaddr_in cli_addr; /* static = initialised to zeros */
	static struct sockaddr_in serv_addr; /* static = initialised to zeros */
	sprintf(port_str, "%d", port); /* convert port back to string */
	/* change dir */
	if (chdir(webdir) == -1) {
		(void) printf("ERROR: Can't Change to directory %s\n", webdir);
		exit(4);
	}

#ifndef DEBUG
    /* Become deamon + unstopable and no zombies children (= no wait()) */
	if(fork() != 0)
		return 0; /* parent returns OK to shell */
#ifdef __APPLE__
	(void)signal(SIGCHLD, SIG_IGN); /* ignore child death */
#elif __linux__
        (void)signal(SIGCLD, SIG_IGN); /* ignore child death */
#endif
	(void)signal(SIGHUP, SIG_IGN); /* ignore terminal hangups */
	for(i=0;i<32;i++)
		(void)close(i);		/* close open files */
	(void)setpgrp();		/* break away from process group */
#endif	

	/* create a socket, bind it to a port number and start listening to the socket */
	nweb_log(LOG,"nweb starting",port_str,getpid());
	/* setup the network socket */
	if((listenfd = socket(AF_INET, SOCK_STREAM,0)) <0)
		nweb_log(ERROR, "system call","socket",0);
	
	if(port < 0 || port >60000)
		nweb_log(ERROR,"Invalid port number (try 1->60000)",port_str,0);
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(port);
	if(bind(listenfd, (struct sockaddr *)&serv_addr,sizeof(serv_addr)) <0)
		nweb_log(ERROR,"system call","bind",0);
	if( listen(listenfd,64) <0)
		nweb_log(ERROR,"system call","listen",0);
	
#ifdef DEBUG
	if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, (socklen_t*)&length)) < 0)
			nweb_log(ERROR,"system call","accept",0);
	(void)close(listenfd);
	web(socketfd,hit); /* never returns */
#else
	
	for(hit=1; ;hit++) {
		length = sizeof(cli_addr);
		if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, (socklen_t*)&length)) < 0)
			nweb_log(ERROR,"system call","accept",0);

		if((pid = fork()) < 0) {
			nweb_log(ERROR,"system call","fork",0);
		}
		else {
			if(pid == 0) { 	/* child */
				(void)close(listenfd);
				web(socketfd,hit); /* never returns */
			} else { 	/* parent */
				(void)close(socketfd);
			}
		}
	}
#endif
}
